#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

# description
"""
connection config file is managed by bnetd.
bnetd parse common data from connection config file and tells connection plugin common data and file name.
bnetd manages interface and L3-config.
"""

"""
connection config file common content (used by bnetd):
1. connection id
2. connection name
3. connection description
4. auto activate

l3 content (used by bnetd):
1. ip address
2. gateway, dns, ntp-server, routes, etc


if one plugin start failed because interface resource collision, then all the bnetd abort!
It is the admin's fault, not bnetd's. Like IWD needs to fully occupy wireless interface.
plugin README should elaborate what hardware it needs, just like IWD.



system specific connection config files are in "/etc".
no plugin information in system specific connection config file. no connection id information in system specific connection config file.
if multiple plugin occupies one system specific connection config file, then all the bnetd abort!
It is the admin's fault, plugin README should elaborate what system specific connection config file it reads.


enable/disable connection is used for such as disable paid connections.
only available connection is reported to bnetd core, bnetd core can get information of all connections by get_all_connections(), this function supports querying and paging.
it is not expected that user selects single connection, which connection is the best connection should be determined by plugin, activate one or many connection should be also determined by plugin.


connection_available() does report connection-data, because connection can be avail/unvail many times in a short period.

"""

"""
plugin has two config: config-file and connection-manager-specific, both of them can change

"""

"""
template metadata.xml
<?xml version="1.0" encoding="utf-8"?>
<plugin>
  <connection-plugin id="static">
    <description>WAN connection by static configuration</description>
    <executable>plugin.py</executable>
    <need-temp-directory></need-temp-directory>
    <need-data-directory></need-data-directory>
    <access-network></access-network>
  </connection-plugin>
</plugin>
"""

"""
plugin command line argument
{
    "socket-file": XXX,
    "data-directory": XXX,     # if need data directory
    "temp-directory": XXX,     # if need temp directory
}
"""

"""
all connections in a plugin has same properties

bnetd runs as user root, group bnetd.
"""

"""
"local" route table is managed by kernel.
 nsswitch.conf has "files myhostname bnetd"

without netns: (networkmanager mode)
  level 1 connection plugin-process should be run as user bnetd-connection-level-1, group bnetd
  level 1 connection plugin-process should be denied for packet interaction with outer world by "iptables --owner bnetd-level-1".
  bnetd denies any dns resolution according to the current user
  one lv1-connection can create per-connection process using LD_PRELOAD so as to access interface created by itself, not-using routing table and glibc dns client.

  calculated lv1 routes should be in "level-1" route table.
  level 2 connection plugin-process should be run as user bnetd-connection-level-2, group bnetd
  level 2 connection plugin-process should use "local" + "level-1" route table, by policy routing and "ip rule uidrange".
  level 2 connection plugin-process should be restrict to use only level-1-connection-interface with outer world by "iptables --owner bnetd-level-1".
  bnetd uses calculated lv1 dns server to do dns resolution
  one lv2-connection should create per-connection process using LD_PRELOAD to access and only access interface created by itself, not and no-need-to use routing table and glibc dns client.

  calculated lv1+lv2 routes should be in "main" route table.
  user configured routes should be in "main" route table.
  applications should use "local" + "main" route table.
  bnetd uses calculated lv1+lv2 dns server to do dns resolution

with netns: (router mode)
  level 1 & 2 connections, vpns should be in network namespace "bnetd"
  bnetd itself is in default network namespace.

  there should be a bridge interface in "bnetd" network namespace, ip address is set on this interface, dhcp server is run on this interface.
  a veth interface joins this brige, the other side of veth is in default network namespace, which has static ip allocated by bnetd.

  applications should be run in default network namespace.
  applications only sees the veth interface.


LD_PRELOAD is similar to torsock project:
1. replace gethostbyaddr() series function
2. replace connect() series function, force bind to interface (SO_BINDTODEVICE, patch the kernel to have IP_NEXTHOP)
3. replace listen() series function, force bind to interface (SO_BINDTODEVICE, patch the kernel to have IP_NEXTHOP)

*. what if the connection has multiple interfaces
*. need to disable rp_filter? I don't want to do it globally.


level 1 connection:
1. physical interface enablement
2. modem
3. pppoe dail up

level 2 connection:
1. vpn client
2. overlay network client, tor/i2p
3. channel as http-proxy / socks-proxy

level 2 app:
1. vpn server?


privnet is VPN, but not vpn-connection. privnet is in lv99.


connections compose a dynamic graph by dependencies.
dep is specified statically.
dependencies is calculated by bnetd itself according to connection-data returned when connection is activated.
connection without dep is level 1.
connection can depend on multiple connections, with different level.
dep can dynamically change according to connection activate event.
every connection has a routing table / dns etc, they are calculate and changed by bnetd.


connection filter algorithm:
1. wired, wireless, mobile
2. charged, free

connection tempory select:
1. user can tempory select some connection, until the selection is cleared

connection selection algorithm:
1. base priority is 0
2. wired connection: 









"""







"""
About local DNS server

bnetd export "/run/bnetd/dns.socket", it's a datagram socket, wire protocol is pure dns protocol.
bnetd provides nss_bnetd.so to use the above socket file, in a limitted manner (nss framework only supports name->ip, ip->name).
application can communicate with the socket file directly, to do any kind of dns query.

bnetd thinks all clients should:
1. get dns info from bnetd only. other means of getting dns info is suboptimal.
2. not doing any dns cache. bnetd caches it.

this socket file only works in lv99 (default network namespace).

bind mount /run/bnetd if dns is need in chroot (it is standard operation)

bnetd should offer API to generate a resolv.conf content?
"""






# config file: ${ETC}/plugin-(PLUGIN_NAME).cfg
class TemplatePlugin:

    def disose(self):
        assert False

    def config_changed(self, new_config):
        pass

    def get_my_obconn_config_files(self):
        # return list<connection-config-file-name>
        pass

    def connection_config_file_added(self, connection_config_file_name):
        pass

    def connection_config_file_removed(self, connection_config_file_name):
        pass

    def connection_config_file_changed(self, connection_config_file_name):
        pass

    def get_all_connections(self, query):
        # return dict<connection-id,connection-data>
        # returns all the connections (including avaiable connections and unavaiable connections)
        # query == "" means get all connections
        pass

    def get_connection(self, connection_id):
        # return connection-data
        pass

    def start(self, callbacks):
        assert False

    def stop(self):
        assert False

    def refresh(self):
        # re-test connection-available etc.
        assert False

    def enable_connection(self, connection_id):
        assert False

    def disable_connection(self, connection_id):
        assert False

    def is_obconn_enabled(self, connection_id):
        assert False


class TemplatePluginCallback:

    def connection_available(self, connection_id):
        pass

    def connection_unavailable(self, connection_id, reason):
        pass

    def connection_activated(self, connection_id, connection_livedata):
        """
        connection_livedata example:
        {
            "interface": [
                {
                    "ifname": "INTERFACE-NAME",
                    "ifconfig": {
                        "inet": {
                            "address": "X.X.X.X",
                            "prefixlen": N,
                        },
                        "inet6": {
                            "address": "X.X.X.X",
                            "prefixlen": N,
                        },
                    }
                    "route": {
                        "inet": [
                            {
                                "target-network": [
                                    "- 10.0.0.0/8",
                                    "+ 0.0.0.0/0",
                                ],
                                "nexthop": [ "IP", "IP" ],
                            },
                        ]
                        "inet6": [
                            {
                                "target-network": [
                                    "- XXX",
                                    "+ XXX",
                                ],
                                "nexthop": [ "IP", "IP" ],
                            },
                        ]
                    }
                    "nameserver": [
                        {
                            "target-domain": [
                                "- google.com",
                                "+ .",
                            ],
                            "protocol": "dns",
                            "inet": [ "IP", "IP:PORT" ],
                            "inet6": [ "IP", "IP:PORT" ],
                        },
                    ],
                    "http-proxy": [
                        {
                            "target-domain": [
                                "- google.com",
                                "+ .",
                            ],
                            "target-network": [
                                "- 10.0.0.0/8",
                                "+ 0.0.0.0/0",
                            ],
                            "url": [ URL, URL ],
                        },
                    ],
                    "socks-proxy": {
                        XXX,
                    },
                    "ntp-server": {
                        "inet": [ IP, IP ],
                        "inet6": [ IP, IP ],
                    },
                    "wins-server": {
                        "inet": [ IP, IP ],
                        "inet6": [ IP, IP ],
                    },
                },
            ],
        }
        """
        pass

    def connection_deactivated(self, connection_id, reason):
        pass
